/* eslint-disable react-hooks/exhaustive-deps */
import React, {useEffect, useRef, useState} from 'react';
import {
  Platform,
  NativeEventEmitter,
  StyleSheet,
  Text,
  View,
  ToastAndroid,
  Button,
  Dimensions,
  PermissionsAndroid,
} from 'react-native';
import BleManager from 'react-native-ble-manager';
import type {Characteristic} from 'react-native-ble-manager';
import {NativeModules} from 'react-native';
import AV from 'leancloud-storage/core';
import * as adapters from '@leancloud/platform-adapters-react-native';
import * as echarts from 'echarts/core';
import {LineChart, GaugeChart} from 'echarts/charts';
import {GridComponent} from 'echarts/components';
import {SVGRenderer, SkiaChart} from '@wuba/react-native-echarts';
import Spinner from 'react-native-loading-spinner-overlay';
import RadioButtonGroup from './radio-button-group';
import dayjs from 'dayjs';
import Loading from './loading';
import NoData from './no-data';

echarts.use([SVGRenderer, LineChart, GridComponent, GaugeChart]);
AV.setAdapters(adapters as unknown as any);

AV.debug.enable();
AV.init({
  appId: 'qGULUxnOQky5advIxDedUGmC-MdYXbMMI',
  appKey: 'GARYnE1gT7gOw43DDGp3QnyG',
  serverURL: 'https://api.catyou.shop',
});

const BleManagerModule = NativeModules.BleManager;
const bleManagerEmitter = new NativeEventEmitter(BleManagerModule);

let uuid: string = '';
let characteristicInfo: Characteristic;
let weightBytes: Array<number> = [];
let isRequesting: boolean = false;
let chart: echarts.ECharts | null = null;
let measureChart: echarts.ECharts | null = null;
let isFetching: boolean = false;
const options = [
  {
    label: '近3天',
    value: '1',
  },
  {
    label: '近一周',
    value: '2',
  },
  {
    label: '近一个月',
    value: '3',
  },
];

interface WeightRecord {
  weight: number;
  date: string;
}

function App(): JSX.Element {
  const weightChartRef = useRef(null);
  const weightMeasureRef = useRef(null);
  const [isReceiveData, setIsReceiveData] = useState(false);
  const [isConnectedBluebooth, setIsConnectedBluebooth] = useState(false);
  const [isShowSpinner, setIsShowSpinner] = useState(true);
  const [spinnerText, setSpinnerText] = useState('');
  const [betweenTime, setBetweenTime] = useState('1');
  const [isLoading, setIsShowLoading] = useState(true);
  const [isNoData, setIsNoData] = useState(false);

  const processWeightBytes = (): void => {
    const weight2 = weightBytes[2];
    const weight1 = weightBytes[1];
    const weightStr = weight2.toString(16) + weight1.toString(16);
    const weight = (parseInt(weightStr, 16) / 200.0) * 2;
    const date = dayjs().format('YYYY-MM-DD');

    const weightRecord = {
      weight,
      date,
    };

    // 脏数据
    if (weight < 50) {
      return;
    }

    drawWeightMeasureChart(weightRecord);

    if (!isRequesting) {
      saveWeightsToCloud(weightRecord);
    }
  };

  const saveWeightsToCloud = (weightRecord: WeightRecord): void => {
    const query = new AV.Query('weightRecord');
    const saveObj = new AV.Object('weightRecord');
    saveObj.set('weight', weightRecord.weight);
    saveObj.set('date', weightRecord.date);
    isRequesting = true;

    query
      .equalTo('date', weightRecord.date)
      .first()
      .then(res => {
        if (res) {
          // 更新
          const updateObj = AV.Object.createWithoutData(
            'weightRecord',
            res.id!,
          );
          updateObj.set('weight', weightRecord.weight);
          updateObj.save().then(() => {
            isRequesting = false;
          });
        } else {
          // 添加
          saveObj.save().then(() => {
            isRequesting = false;
          });
        }
      })
      .catch(error => {
        ToastAndroid.show(error, 3);
      });
  };

  const drawWeightMeasureChart = (weightRecord: WeightRecord) => {
    const option = {
      series: [
        {
          type: 'gauge',
          axisLine: {
            lineStyle: {
              width: 10,
              color: [
                [0.3, '#67e0e3'],
                [0.7, '#37a2da'],
                [1, '#fd666d'],
              ],
            },
          },
          pointer: {
            itemStyle: {
              color: 'auto',
            },
          },
          axisTick: {
            distance: -30,
            length: 8,
            lineStyle: {
              color: '#fff',
              width: 2,
            },
          },
          splitLine: {
            distance: -30,
            length: 30,
            lineStyle: {
              color: '#fff',
              width: 4,
            },
          },
          axisLabel: {
            color: 'inherit',
            distance: 15,
            fontSize: 18,
          },
          detail: {
            valueAnimation: true,
            formatter: '{value}斤',
            color: 'inherit',
            fontSize: 22,
            lineHeight: 25,
            offsetCenter: [0, 30],
          },
          data: [
            {
              value: weightRecord.weight,
            },
          ],
        },
      ],
    };

    if (weightMeasureRef.current) {
      if (!measureChart) {
        measureChart = echarts.init(weightMeasureRef.current, 'light', {
          renderer: 'svg',
          width: 230,
          height: 230,
        });
      }

      measureChart.setOption(option);
    }
  };

  const startReceiveData = () => {
    BleManager.startNotification(
      uuid,
      characteristicInfo.service,
      characteristicInfo.characteristic,
    )
      .then(() => {
        setIsReceiveData(true);
        BleManager.write(
          uuid,
          characteristicInfo.service,
          characteristicInfo.characteristic,
          '01FFFFFFFF',
        );

        BleManager.write(
          uuid,
          characteristicInfo.service,
          characteristicInfo.characteristic,
          '02',
        );
      })
      .catch(error => {
        ToastAndroid.show(`监听小米体重秤失败: ${error}`, 3);
      });
  };

  const stopReceiveData = () => {
    BleManager.stopNotification(
      uuid,
      characteristicInfo.service,
      characteristicInfo.characteristic,
    )
      .then(() => {
        setIsReceiveData(false);
        BleManager.write(
          uuid,
          characteristicInfo.service,
          characteristicInfo.characteristic,
          '03',
        );

        BleManager.write(
          uuid,
          characteristicInfo.service,
          characteristicInfo.characteristic,
          '04FFFFFFFF',
        );
      })
      .catch(error => {
        ToastAndroid.show(`停止监听小米体重秤失败: ${error}`, 3);
      });
  };

  const initBluetooth = () => {
    bleManagerEmitter.addListener(
      'BleManagerDidUpdateValueForCharacteristic',
      ({value}) => {
        weightBytes = value;
        processWeightBytes();
      },
    );

    bleManagerEmitter.addListener('BleManagerStopScan', () => {
      setSpinnerText('正在获取可连接设备...');
      BleManager.getDiscoveredPeripherals().then(peripheralsArray => {
        const miScale = peripheralsArray.find(({name}) => name === 'MI SCALE2');

        if (Platform.OS === 'android') {
          if (miScale) {
            setSpinnerText('正在连接小米体重秤...');
            uuid = miScale.id;
            BleManager.connect(uuid)
              .then(() => {
                setSpinnerText('');
                setIsShowSpinner(false);
                ToastAndroid.show('连接小米体重秤成功', 3);
                setIsConnectedBluebooth(true);
                // 获取蓝牙Service和Characteristics
                BleManager.retrieveServices(uuid)
                  .then(peripheralInfo => {
                    const info = peripheralInfo!.characteristics!.find(
                      ({characteristic, service}) =>
                        characteristic === '2a9d' && service === '181d',
                    );

                    if (info) {
                      characteristicInfo = info;
                    }
                  })
                  .catch(error => {
                    ToastAndroid.show(
                      `获取小米体重秤蓝牙特征失败: ${error}`,
                      3,
                    );
                  });
              })
              .catch(error => {
                ToastAndroid.show(`蓝牙连接失败: ${error}`, 3);
              });
          } else {
            setIsShowSpinner(false);
            ToastAndroid.show('未找到小米体重秤', 3);
          }
        }
      });
    });

    try {
      BleManager.start({showAlert: true})
        .then(() => {
          setSpinnerText('正在开启蓝牙...');
          BleManager.enableBluetooth()
            .then(() => {
              setSpinnerText('');
              setIsShowSpinner(false);
            })
            .catch(error => {
              setIsShowSpinner(false);
              ToastAndroid.show(
                `蓝牙打开失败，请确认您是否已打开蓝牙: ${error}`,
                3,
              );
            });
        })
        .catch(error => {
          setIsShowSpinner(false);
          ToastAndroid.show(
            `蓝牙打开失败，请确认您是否已打开蓝牙: ${error}`,
            3,
          );
        });
    } catch (error) {
      setIsShowSpinner(false);
      ToastAndroid.show(`初始化蓝牙失败: ${error}`, 3);
    }
  };

  const fetchHistoryWeightData = (newBetweenTime = betweenTime) => {
    isFetching = true;
    setIsNoData(false);
    ToastAndroid.show('获取历史体重数据...', 3);

    let startDate;
    let endDate;

    // 近三天
    if (newBetweenTime === '1') {
      startDate = dayjs().subtract(2, 'day').format('YYYY-MM-DD');
      endDate = dayjs().add(1, 'day').format('YYYY-MM-DD');
    }

    // 近一周
    if (newBetweenTime === '2') {
      startDate = dayjs()
        .subtract(1, 'week')
        .add(1, 'day')
        .format('YYYY-MM-DD');
      endDate = dayjs().add(1, 'day').format('YYYY-MM-DD');
    }

    // 近一个月
    if (newBetweenTime === '3') {
      startDate = dayjs()
        .subtract(1, 'month')
        .add(1, 'day')
        .format('YYYY-MM-DD');
      endDate = dayjs().add(1, 'day').format('YYYY-MM-DD');
    }

    const startDateQuery = new AV.Query('weightRecord');
    startDateQuery.greaterThanOrEqualTo(
      'createdAt',
      new Date(`${startDate} 00:00:00`),
    );

    const endDateQuery = new AV.Query('weightRecord');
    endDateQuery.lessThan('createdAt', new Date(`${endDate} 00:00:00`));
    const query = AV.Query.and(startDateQuery, endDateQuery);

    query
      .find()
      .then(res => {
        setIsShowLoading(false);
        const weightList = res.map(item => item.toJSON());

        if (weightList.length === 0) {
          setIsNoData(true);
        } else {
          setIsNoData(false);
          const option = {
            grid: {
              top: 40,
              bottom: 40,
            },
            xAxis: {
              type: 'category',
              data: weightList.map(({date}) => date),
              axisTick: {
                show: false,
              },
              axisLine: {
                show: false,
                lineStyle: {
                  color: '#333',
                },
              },
              axisLabel: {
                color: '#333',
                interval: 0,
                rotate: 30,
                formatter: (val: string) => dayjs(val).format('YY-MM-DD'),
              },
            },
            yAxis: {
              type: 'value',
              name: '体重(斤)',
              nameTextStyle: {
                align: 'center',
                padding: [0, 0, 5, -12],
              },
              axisLine: {
                show: false,
                lineStyle: {
                  color: '#333',
                },
              },
              splitLine: {
                lineStyle: {
                  type: 'dashed',
                  color: '#eee',
                },
              },
              min: 0,
              max: 200,
              splitNumber: 4,
            },
            series: [
              {
                data: weightList.map(({weight}) => weight),
                type: 'line',
                label: {
                  show: true,
                },
                smooth: true,
                showSymbol: true,
                symbol: 'circle',
                symbolSize: 8,
                areaStyle: {
                  color: {
                    type: 'linear',
                    x: 0,
                    y: 0,
                    x2: 0,
                    y2: 1,
                    colorStops: [
                      {
                        offset: 0,
                        color: 'blue', // 0% 处的颜色
                      },
                      {
                        offset: 1,
                        color: '#fff', // 100% 处的颜色
                      },
                    ],
                    global: false, // 缺省为 false
                  },
                },
                itemStyle: {
                  color: 'rgb(33, 150, 243)',
                  borderColor: '#fff',
                  borderWidth: 2,
                },
              },
            ],
          };

          if (weightChartRef.current) {
            if (!chart) {
              chart = echarts.init(weightChartRef.current, 'light', {
                renderer: 'svg',
                width: Dimensions.get('window').width - 20,
                height: 220,
              });
            }

            chart.setOption(option);
          }
        }
      })
      .catch(error => {
        setIsShowLoading(false);
        ToastAndroid.show(`获取体重历史数据失败: ${error}`, 3);
      })
      .finally(() => {
        isFetching = false;
      });
  };

  const disconnectBluetooth = () => {
    BleManager.disconnect(uuid)
      .then(() => {
        setIsConnectedBluebooth(false);
      })
      .catch(error => {
        ToastAndroid.show(`蓝牙连接失败: ${error}`, 3);
      });
  };

  const connectBluetooth = () => {
    setSpinnerText('正在扫描蓝牙设备...');
    setIsShowSpinner(true);

    BleManager.scan([], 5, true).catch(error => {
      setIsShowSpinner(false);
      ToastAndroid.show(`蓝牙扫描失败: ${error}`, 3);
    });
  };

  const onChangeWeightBetweenTime = (val: string) => {
    setBetweenTime(val);
    fetchHistoryWeightData(val);
  };

  useEffect(() => {
    initBluetooth();
    fetchHistoryWeightData();
  }, []);

  return (
    <View style={styles.container}>
      <Spinner visible={isShowSpinner} textContent={spinnerText} />
      <View style={styles.header}>
        <Text style={styles.headerText}>体重记录</Text>
      </View>
      {!isLoading ? (
        !isNoData ? (
          <View style={styles.radioGroupContainer}>
            <RadioButtonGroup
              value={betweenTime}
              options={options}
              onChange={onChangeWeightBetweenTime}
            />
          </View>
        ) : null
      ) : null}
      {!isLoading ? (
        isNoData ? (
          <NoData />
        ) : (
          <View style={styles.chart}>
            <SkiaChart ref={weightChartRef} />
          </View>
        )
      ) : (
        <Loading />
      )}
      <View style={styles.opContainer}>
        <View>
          <SkiaChart ref={weightMeasureRef} />
        </View>
        <View style={styles.btnContainer}>
          <View style={styles.refreshBtn}>
            <Button
              title="刷新体重图表"
              onPress={() => {
                !isFetching && fetchHistoryWeightData();
              }}
            />
          </View>
          <View style={styles.connectOrDisconnctBtn}>
            <Button
              title={isConnectedBluebooth ? '断开蓝牙' : '连接蓝牙'}
              onPress={
                isConnectedBluebooth ? disconnectBluetooth : connectBluetooth
              }
            />
          </View>
          <Button
            disabled={!isConnectedBluebooth}
            title={isReceiveData ? '停止接收数据' : '开始接收数据'}
            onPress={isReceiveData ? stopReceiveData : startReceiveData}
          />
        </View>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    display: 'flex',
    flexDirection: 'column',
    height: '100%',
    backgroundColor: '#fff',
  },
  header: {
    display: 'flex',
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    height: 40,
    lineHeight: 40,
    borderBottomColor: '#eee',
    borderBottomWidth: 1,
  },
  headerText: {
    fontSize: 18,
    fontWeight: 'normal',
    color: '#333',
  },
  chart: {
    marginLeft: 10,
    marginRight: 10,
  },
  opContainer: {
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    marginTop: 20,
  },
  btnContainer: {
    marginTop: -10,
    width: '80%',
  },
  refreshBtn: {
    marginBottom: 10,
  },
  connectOrDisconnctBtn: {
    marginBottom: 10,
  },
  radioGroupContainer: {
    display: 'flex',
    flexDirection: 'row',
    justifyContent: 'flex-end',
    alignItems: 'center',
    marginTop: 5,
  },
});

export default App;
